package game.data;

import echo.math.Vector2;
import echo.shape.Polygon;
import echo.Body;
import hx.geom.Point;
import game.data.ABProjectData.ABRoleAttrData;
import game.data.ABProjectData.ABHitData;
import echo.Shape;
import game.data.ABProjectData.ABCollision;
import game.data.ABProjectData.ABEffectData;
import game.data.ABProjectData.ABFrameData;
import game.data.ABProjectData.ABActionData;
import haxe.io.Path;
import hx.assets.Assets;
import haxe.Json;

using Reflect;
using game.utils.StringsTools;

class ProjectRoleData implements IRoleData {
	/**
	 * 事件Tag
	 */
	public var eventTag:String;

	/**
	 * 精灵图偏移X
	 */
	public var offestX:Float = 0;

	/**
	 * 精灵图偏移Y
	 */
	public var offestY:Float = 0;

	/**
	 * 编辑器版本号
	 */
	public var editerVersion:String;

	/**
	 * 构建版本号
	 */
	public var buildVersion:String;

	public var offestConfig:Dynamic<{
		x:Float,
		y:Float
	}>;

	/**
	 * 精灵图映射
	 */
	public var atlas:Array<String> = [];

	/**
	 * 动作数据
	 */
	public var actions:Map<String, ActionData> = [];

	/**
	 * 组合键数组，一般是经过了排序，由长组合键到短组合键
	 */
	public var groupKeyActions:Array<ActionData> = [];

	/**
	 * 角色属性数据
	 */
	public var attrData:RoleAttrData = new RoleAttrData();

	/**
	 * 扩展数据
	 */
	public var data:Dynamic = {};

	/**
	 * 角色数据
	 * @param data 
	 */
	public function new(?data:String) {
		if (data != null)
			parse(Json.parse(data));
	}

	/**
	 * 解析帧数据
	 * @param data 
	 * @return Array<FrameData>
	 */
	private function parseABFrameDataToFrameData(data:Array<ABFrameData>):Array<FrameData> {
		var frameData:Array<FrameData> = [];
		for (i in 0...data.length) {
			var abFrameData = data[i];
			var fData = new FrameData();
			fData.frame = i;
			if (abFrameData.collision == null) {
				fData.collision = null;
			} else if (!Std.isOfType(abFrameData.collision, Array)) {
				if (abFrameData.collision.length > 0) {
					fData.collision = new Body();
					parseABCollision(fData.collision, untyped abFrameData.collision);
				}
			} else {
				fData.collision = new Body();
				if (abFrameData.collision.length > 0) {
					for (collision in abFrameData.collision) {
						parseABCollision(fData.collision, collision);
					}
				}
			}
			fData.collisionLiveTime = abFrameData.collisionLiveTime;
			fData.effects = parseABEffects(abFrameData.effects);
			for (data in fData.effects) {
				data.frameData = fData;
			}
			fData.eventTag = abFrameData.eventTag == "无" || abFrameData.eventTag == null ? "" : abFrameData.eventTag;
			fData.hitData = parseABHitData(abFrameData.hitData);
			fData.isStop = abFrameData.isStop;
			fData.stopAction = abFrameData.stopAction;
			fData.originMoveX = abFrameData.moveX;
			fData.originMoveY = abFrameData.moveY;
			fData.originAlpha = abFrameData.alpha;
			fData.originScaleX = abFrameData.scaleX;
			fData.originScaleY = abFrameData.scaleY;
			fData.originRotate = abFrameData.rotate;
			fData.originPointX = abFrameData.pointX;
			fData.originPointY = abFrameData.pointY;
			fData.shakeIntensity = abFrameData.shakeIntensity;
			fData.shakeDuration = abFrameData.shakeDuration;
			fData.shakeFrequency = abFrameData.shakeFrequency;
			fData.invincibleTime = abFrameData.invincibleTime;
			fData.stoicTime = abFrameData.stoicTime;
			fData.name = abFrameData.name;
			fData.sound = abFrameData.sound;
			fData.shareCollision = abFrameData.shareCollision;
			frameData.push(fData);
		}
		return frameData;
	}

	private function parseABHitData(data:ABHitData):HitData {
		if (data == null)
			return new HitData();
		var hitData:HitData = new HitData();
		for (key in Reflect.fields(data)) {
			if (key == "hitColor") {
				var string:String = Reflect.getProperty(data, key);
				hitData.hitColor = Std.parseInt(string);
			} else {
				Reflect.setProperty(hitData, key, data.getProperty(key));
			}
		}
		return hitData;
	}

	private function parseABCollision(body:Body, data:ABCollision):Shape {
		if (data == null)
			return null;
		switch data.type {
			case POLYGON:
				var shape = Polygon.get();
				shape.set_vertices([
					for (array in data.points) {
						new Vector2(array[0], array[1]);
					}
				]);
				body.add_shape(shape);
				return shape;
			case CIRCLE:
		}
		return null;
	}

	private function parseABEffects(data:Array<ABEffectData>):Array<EffectData> {
		var effects:Array<EffectData> = [];
		for (i in 0...data.length) {
			var abEffectData = data[i];
			var effectData = new EffectData();
			for (key in Reflect.fields(abEffectData)) {
				if (key == "speed") {
					Reflect.setProperty(effectData, key, parsePoint(abEffectData.getProperty(key)));
				} else if (key == "frames") {
					Reflect.setProperty(effectData, key, parseABFrameDataToFrameData(abEffectData.getProperty(key)));
					for (data in effectData.frames) {
						data.timeline = effectData;
					}
				} else {
					Reflect.setProperty(effectData, key, abEffectData.getProperty(key));
				}
			}
			effectData.updateActionData();
			effects.push(effectData);
		}
		return effects;
	}

	/**
	 * 解析角色动作数据
	 * @param data 
	 */
	private function parseABActionDataToActionData(data:Dynamic<ABActionData>):Void {
		for (key in data.fields()) {
			var actionData:ActionData = new ActionData();
			var action:ABActionData = data.getProperty(key);
			actionData.allowAirMove = action.allowAirMove;
			actionData.diabledForce = action.diabledForce;
			actionData.airGravity = action.airGravity ?? 0.2;
			actionData.cd = action.cd;
			actionData.desc = action.desc;
			actionData.fps = action.fps;
			actionData.frames = parseABFrameDataToFrameData(action.frames);
			for (data in actionData.frames) {
				data.timeline = actionData;
			}
			actionData.groupKeys = action.groupKeys;
			actionData.hpConsume = action.hpConsume;
			actionData.mpConsume = action.mpConsume;
			actionData.effortConsume = action.effortConsume ?? 0;
			actionData.name = action.name;
			if (actionData.name == "瞬步") {
				actionData.effortConsume = 10;
				actionData.diabledForceFirst = true;
			}
			actionData.type = action.type;
			this.actions.set(action.name, actionData);
			actionData.updateActionData();
			if (actionData.groupKeys != null && actionData.groupKeys.length > 0) {
				groupKeyActions.push(actionData);
			}
		}
		groupKeyActions.sort((a, b) -> a.groupKeys.length > b.groupKeys.length ? -1 : 1);
	}

	private function parsePoint(data:Dynamic):Point {
		if (data == null)
			return null;
		return new Point(data.x, data.y);
	}

	/**
	 * 解析数据结构
	 * @param parent 
	 * @param data 
	 */
	private function parse(data:ABProjectData):Void {
		// 纹理数据
		for (key in data.atlas) {
			this.atlas.push(key);
		}
		// 解析动作数据
		this.parseABActionDataToActionData(data.actions);
		// 解析偏移值
		this.offestX = 0;
		this.offestY = 0;
		this.offestConfig = data.offestConfig ?? {};
		// 解析角色属性
		this.attrData = parseABRoleData(data.attrData);
	}

	public function parseABRoleData(data:ABRoleAttrData):RoleAttrData {
		var ret:RoleAttrData = new RoleAttrData();
		for (key in data.fields()) {
			ret.setProperty(key, data.getProperty(key));
		}
		return ret;
	}

	/**
	 * 解析所需的加载资源到Assets对象中
	 * @param assets 
	 */
	public function parseLoadFilesToAssets(assets:Assets, parentDir:String):Void {
		var files = getAssetsFiles();
		for (file in files) {
			switch file.type {
				case ATLAS:
					assets.loadAtlas(Path.join([parentDir, "images", file.path]), Path.join([parentDir, "images", Path.withoutExtension(file.path) + ".xml"]));
				case SOUND:
					assets.loadSound(Path.join([parentDir, "sound", file.path]));
			}
		}
	}

	/**
	 * 追加资源配置
	 * @param files 
	 * @param file 
	 */
	public function pushAssetsFile(files:Array<AssetsFile>, file:AssetsFile):Void {
		for (f in files) {
			if (f.path == file.path) {
				return;
			}
		}
		files.push(file);
	}

	/**
	 * 读取FrameData中的所有资源数据
	 * @param frame 
	 * @param array 
	 * @return Array<AssetsFile>
	 */
	public function getAssetsFileByFrameData(frame:FrameData, array:Array<AssetsFile>):Array<AssetsFile> {
		if (frame.hitData != null) {
			if (frame.hitData.sound.isNotNull()) {
				pushAssetsFile(array, {
					path: frame.hitData.sound + ".mp3",
					type: "sound"
				});
			}
		}
		if (frame.sound.isNotNull()) {
			pushAssetsFile(array, {
				path: frame.sound + ".mp3",
				type: "sound"
			});
		}
		if (frame.hitData.hitEffectId.isNotNull()) {
			pushAssetsFile(array, {
				path: frame.hitData.hitEffectId + ".png",
				type: "atlas"
			});
		}
		// effects: name / sound
		if (frame.effects != null) {
			for (effect in frame.effects) {
				if (effect.sound.isNotNull()) {
					pushAssetsFile(array, {
						path: effect.sound + ".mp3",
						type: "sound"
					});
				}
				if (effect.frames != null)
					for (frame in effect.frames) {
						getAssetsFileByFrameData(frame, array);
					}
				if (effect.name.isNotNull()) {
					pushAssetsFile(array, {
						path: effect.name + ".png",
						type: "atlas"
					});
				}
			}
		}
		return array;
	}

	public function getAssetsFiles(array:Array<AssetsFile> = null):Array<AssetsFile> {
		if (array == null)
			array = [];
		if (attrData.hitBlowEffectId.isNotNull()) {
			pushAssetsFile(array, {
				path: attrData.hitBlowEffectId + ".png",
				type: "atlas"
			});
		}
		if (attrData.hitEffectId.isNotNull()) {
			pushAssetsFile(array, {
				path: attrData.hitEffectId + ".png",
				type: "atlas"
			});
		}
		if (this.attrData.hitSound.isNotNull()) {
			pushAssetsFile(array, {
				path: this.attrData.hitSound + ".mp3",
				type: "sound"
			});
		}
		if (this.attrData.hitSound2.isNotNull()) {
			pushAssetsFile(array, {
				path: this.attrData.hitSound2 + ".mp3",
				type: "sound"
			});
		}
		for (data in this.actions) {
			for (frame in data.frames) {
				getAssetsFileByFrameData(frame, array);
			}
		}
		// atlas: keys
		for (image in atlas) {
			pushAssetsFile(array, {
				path: image + ".png",
				type: "atlas"
			});
		}
		return array;
	}
}

typedef AssetsFile = {
	var path:String;
	var type:AssetsFileType;
}

enum abstract AssetsFileType(String) to String from String {
	var ATLAS = "atlas";
	var SOUND = "sound";
}
